﻿//
// XN4SL - Xna Framework for Silverlight.
//
using Xn4Sl.Framework.Input;
using Xn4Sl.Framework.Content;
using Xn4Sl.Framework.Graphics;
using System;
using System.Windows;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Controls;

namespace Xn4Sl.Framework
{
    /// <summary>
    /// Provides basic graphics device initialization, game logic, and rendering code.
    /// </summary>
    public class Game : UserControl, IDisposable
    {
        /// <summary>
        /// Visual interface element for game component.
        /// </summary>
        private Canvas LayoutRoot = new Canvas() { Background = new SolidColorBrush(Colors.Black) };

        /// <summary>
        /// Game time information.
        /// </summary>
        private GameTime gameTime;
        private TimeSpan startTime;

        /// <summary>
        /// Initializes a new instance of Game class, which provides basic graphics device
        /// initialization, game logic, and a game loop.
        /// </summary>
        public Game() 
        {
            //
            // Create GraphicsDeviceManager and GraphicsDevice.
            //
            this.graphicsDevice = new GraphicsDevice(this.LayoutRoot);
            GraphicsDeviceManager graphicsDeviceManager = new GraphicsDeviceManager(this);
            this.services.AddService(typeof(GraphicsDeviceManager), graphicsDeviceManager);
            //
            // Create ContentManager.
            //
            this.Content = new ContentManager(this.services);
            //
            // Assign control root element as a control content.
            //
            base.Content = this.LayoutRoot;
            base.Content.RenderTransform = new ScaleTransform();
            //
            // Initialize game.
            //
            this.Initialize();
        }

        /// <summary>
        /// Called after the Game and Graphics.GraphicsDevice are created, but before
        /// Game.LoadContent.
        /// </summary>
        protected virtual void Initialize()
        {
            //
            // Attach sound elements to the control root element.
            //
            this.LayoutRoot.Children.Add(Xn4Sl.Framework.Media.MediaPlayer.Content);
            this.LayoutRoot.Children.Add(Xn4Sl.Framework.Audio.SoundPlayer.Content);
            //
            // Bind to events.
            //
            this.Loaded += new RoutedEventHandler(Game_Loaded);
            this.KeyDown += new KeyEventHandler(Game_KeyDown);
            this.KeyUp += new KeyEventHandler(Game_KeyUp);
            this.IsTabStop = true;
            //
            // Hide cursor.
            //
            this.IsMouseVisible = false;
        }

        /// <summary>
        /// Keyboard down events.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Game_KeyDown(object sender, KeyEventArgs e)
        {
            e.Handled = true;
            Xn4Sl.Framework.Input.Keyboard.SetKeyState(Utils.Convert.ToKeys(e.Key), KeyState.Down);
        }

        /// <summary>
        /// Keyboard up events.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Game_KeyUp(object sender, KeyEventArgs e)
        {
            e.Handled = true;
            Xn4Sl.Framework.Input.Keyboard.SetKeyState(Utils.Convert.ToKeys(e.Key), KeyState.Up);
        }

        /// <summary>
        /// Called after the component has completed layout passes, has rendered, and is ready for interaction.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        protected void Game_Loaded(object sender, RoutedEventArgs e) 
        {
            // 
            // Call LoadContent.
            //
            this.LoadContent();
            //
            // Run the game.
            //
            this.Run();
        }

        /// <summary>
        /// Gets the collection of GameComponents owned by the game.
        /// </summary>
        public GameComponentCollection Components
        {
            get
            {
                throw new NotImplementedException();
            }
        }

        /// <summary>
        /// Gets or sets the current Content.ContentManager.
        /// </summary>
        private ContentManager content;
        public new ContentManager Content 
        {
            get
            {
                return this.content;
            }
            set
            {
                this.content = value;
            }
        }

        /// <summary>
        /// Gets the current Framework.Graphics.GraphicsDevice.
        /// </summary>
        private GraphicsDevice graphicsDevice;
        public GraphicsDevice GraphicsDevice 
        { 
            get 
            {
                return this.graphicsDevice;
            } 
        }

        /// <summary>
        /// Gets or sets the time to sleep when the game is inactive.
        /// </summary>
        public TimeSpan InactiveSleepTime { get; set; }

        /// <summary>
        /// Indicates whether the game is currently the active application.
        /// </summary>
        private bool isActive = false;
        public bool IsActive
        {
            get
            {
                return this.isActive;
            }
            set
            {
                this.isActive = value;

                if (value)
                {
                    this.OnActivated(this, null);
                }
                else
                {
                    this.OnDeactivated(this, null);
                }
            }
        }

        /// <summary>
        /// Gets or sets a value indicating whether to use fixed time steps.
        /// </summary>
        private bool isFixedTimeStep = true;
        public bool IsFixedTimeStep 
        {
            get
            {
                return this.isFixedTimeStep;
            }
            set
            {
                this.isFixedTimeStep = value;
            }
        }

        /// <summary>
        /// Gets or sets a value indicating whether the mouse cursor should be visible.
        /// </summary>
        public bool IsMouseVisible 
        {
            get
            {
                return this.LayoutRoot.Cursor == Cursors.None ? false : true;
            }
            set
            {
                this.LayoutRoot.Cursor = value ? Cursors.Arrow : Cursors.None;
            }
        }

        /// <summary>
        /// Gets the GameServiceContainer holding all the service providers attached
        /// to the Game.
        /// </summary>
        private GameServiceContainer services = new GameServiceContainer();
        public GameServiceContainer Services
        {
            get
            {
                return this.services;
            }
        }

        /// <summary>
        /// Gets or sets the target time between calls to Game.Update when Game.IsFixedTimeStep
        /// is true.
        /// </summary>
        private TimeSpan targetElapsedTime = TimeSpan.Zero;
        public TimeSpan TargetElapsedTime 
        {
            get
            {
                return this.targetElapsedTime;
            }
            set
            {
                this.targetElapsedTime = value;
            }
        }

        /// <summary>
        /// Gets the underlying operating system window.
        /// </summary>
        public GameWindow Window
        {
            get
            { 
                throw new NotSupportedException();
            }
        }

        /// <summary>
        /// Raised when the game gains focus.
        /// </summary>
        public event EventHandler Activated;

        /// <summary>
        /// Raised when the game loses focus.
        /// </summary>
        public event EventHandler Deactivated;

        /// <summary>
        /// Raised when the game is being disposed.
        /// </summary>
        public event EventHandler Disposed;

        /// <summary>
        /// Raised when the game is exiting.
        /// </summary>
        public event EventHandler Exiting;

        /// <summary>
        /// Called after all components are initialized but before the first update in
        /// the game loop.
        /// </summary>
        protected virtual void BeginRun() 
        {
            this.IsActive = true;

            //
            // Set game start time.
            //
            this.startTime = new TimeSpan(DateTime.Now.Ticks);
            //
            // Attach the render event handler 
            //
            CompositionTarget.Rendering += new EventHandler(CompositionTarget_Rendering);
        }

        /// <summary>
        /// Called after the game loop has stopped running before exiting.
        /// </summary>
        protected virtual void EndRun()
        {
            this.isActive = false;
            //
            // Unload the render event handler 
            //
            CompositionTarget.Rendering -= new EventHandler(CompositionTarget_Rendering);
        }

        /// <summary>
        /// Exits the game.
        /// </summary>
        public void Exit()
        {
            if (this.Exiting != null) this.Exiting(this, null);
        }

        /// <summary>
        /// Immediately releases the unmanaged resources used by this object.
        /// </summary>
        public void Dispose() 
        {
            if (this.Disposed != null) this.Disposed(this, null);
        }
        
        /// <summary>
        /// Releases all resources used by the Game class.
        /// </summary>
        /// <param name="disposing">true to release both managed and unmanaged resources; 
        /// false to release only unmanaged resources.</param>
        protected virtual void Dispose(bool disposing) { }

        /// <summary>
        /// Starts the drawing of a frame. This method is followed by calls to Game.Draw
        /// and Game.EndDraw.
        /// </summary>
        /// <returns>true if the frame should be drawn; false otherwise.</returns>
        protected virtual bool BeginDraw()
        {
            //
            // Hide all elements.
            //
            foreach (UIElement item in this.graphicsDevice.UI.Children)
            {
                item.Visibility = Visibility.Collapsed;
            }
            return true;
        }

        /// <summary>
        /// Called when the game determines it is time to draw a frame. 
        /// </summary>
        /// <param name="gameTime">Time passed since the last call to 
        /// Xn4Sl.Framework.Game.Draw(Xn4Sl.Framework.GameTime).</param>
        protected virtual void Draw(GameTime gameTime) { }
        
        /// <summary>
        /// Ends the drawing of a frame. This method is preceeded by calls to Game.Draw
        /// and Game.BeginDraw.
        /// </summary>
        protected virtual void EndDraw() 
        {
            this.graphicsDevice.Textures.Clear();
        }

        /// <summary>
        /// Called when graphics resources need to be loaded.
        /// </summary>
        protected virtual void LoadContent() { }
        
        /// <summary>
        /// Raises the Game.Activated event. Override this method to add code to handle
        /// when the game gains focus.
        /// </summary>
        /// <param name="sender">The Game.</param>
        /// <param name="args">Arguments for the Game.Activated event.</param>
        protected virtual void OnActivated(object sender, EventArgs args) 
        {
            if (this.Activated != null) this.Activated(this, null);
        }
        
        /// <summary>
        /// Raises the Game.Deactivated event. Override this method to add code to handle
        //  when the game loses focus.
        /// </summary>
        /// <param name="sender">The Game.</param>
        /// <param name="args">Arguments for the Game.Deactivated event.</param>
        protected virtual void OnDeactivated(object sender, EventArgs args)
        {
            if (this.Deactivated != null) this.Deactivated(this, null);
        }
        
        /// <summary>
        /// Raises an Game.Exiting event. Override this method to add code to handle
        /// when the game is exiting.
        /// </summary>
        /// <param name="sender">The Game.</param>
        /// <param name="args">Arguments for the Game.Exiting event.</param>
        protected virtual void OnExiting(object sender, EventArgs args) { }
        
        /// <summary>
        /// Resets the elapsed time counter.
        /// </summary>
        public void ResetElapsedTime() { }
        
        /// <summary>
        /// Call this method to initialize the game, begin running the game loop, and
        /// start processing events for the game.
        /// </summary>
        public void Run() 
        {
            this.gameTime = new GameTime();
            //
            // Start the game.
            //
            this.BeginRun();
        }
        
        /// <summary>
        /// Called each time the core Silverlight rendering process renders a frame.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void CompositionTarget_Rendering(object sender, EventArgs e)
        {
            //
            // Process game clock tick.
            //
            this.Tick();
        }
        
        /// <summary>
        /// This is used to display an error message if there is no suitable graphics
        /// device or sound card.
        /// </summary>
        /// <param name="exception">The exception to display.</param>
        /// <returns>true if an error was displayed, otherwise false.</returns>
        protected virtual bool ShowMissingRequirementMessage(Exception exception) 
        {
            return false;
        }
        
        /// <summary>
        /// Prevents calls to Game.Draw until the next Game.Update.
        /// </summary>
        public void SuppressDraw()
        {
            throw new NotImplementedException();
        }
        
        /// <summary>
        /// Updates the game's clock and calls Game.Update and Game.Draw.
        /// </summary>
        public void Tick()
        {
            //
            // Get real time factors.
            //
            TimeSpan totalRealTime = new TimeSpan(DateTime.Now.Ticks) - this.startTime;
            TimeSpan elapsedRealTime = totalRealTime - this.gameTime.TotalRealTime;

            if (this.IsActive)
            {
                //
                // In a fixed-step game loop, Game calls Update once the TargetElapsedTime has elapsed. 
                //                
                if (IsFixedTimeStep && elapsedRealTime < targetElapsedTime)
                {
                    return;
                }

                //
                // Limit elapsed game time value.
                //
                TimeSpan elapsedGameTime = elapsedRealTime > targetElapsedTime ?
                                                             targetElapsedTime :
                                                             elapsedRealTime;

                this.gameTime = new GameTime(totalRealTime, 
                                             elapsedRealTime, 
                                             this.gameTime.TotalGameTime + elapsedGameTime, 
                                             elapsedGameTime);

                //
                // Call update.
                //
                this.Update(this.gameTime);

                this.gameTime.IsRunningSlowly = false;
                TimeSpan elapsed = this.gameTime.TotalRealTime + this.startTime - new TimeSpan(DateTime.Now.Ticks);

                //
                // After Update is called, if it is not time to call Update again, Game calls Draw. 
                //
                if (elapsed < targetElapsedTime) 
                {
                    this.BeginDraw();
                    this.Draw(this.gameTime);
                    this.EndDraw();
                }
                else
                {
                    this.gameTime.IsRunningSlowly = true;
                    this.Update(this.gameTime);
                }
            }
        }
        
        /// <summary>
        /// Called when graphics resources need to be unloaded. Override this method
        /// to unload any game-specific graphics resources.
        /// </summary>
        protected virtual void UnloadContent() { }
        
        /// <summary>
        /// Called when the game has determined that game logic needs to be processed.
        /// </summary>
        /// <param name="gameTime">Time passed since the last call to Game.Update.</param>
        protected virtual void Update(GameTime gameTime) { }
    }
}
